<?php
///////////////////////////////////////////////////////////////////////////////
/*
edge.php

Class Edge - class containing methods for retrieving and saving edge data.


*/

class Edge {

    private $id;
    private $sourcenode_id;
    private $targetnode_id;
    private $width;
    private $sourceleveltype_id;
    private $targetleveltype_id;

    public static $TABLE_EDGE = TABLE_EDGE;
    public static $TEMP_TABLE_EDGE = TEMP_TABLE_EDGE;


    /////////////////////////////////////////////////////////////////////////////
    // contructor - instantiates edge object with supplied attributes
    // function __construct($id, $sourcenode_id, $targetnode_id, $width, $sourceleveltype_id, $targetleveltype_id) {
    public function __construct($id = null, $sourcenode_id = null, $targetnode_id = null, $width = null,
                                $sourceleveltype_id = null, $targetleveltype_id = null) {
        $this->init($id, $sourcenode_id, $targetnode_id, $width, $sourceleveltype_id, $targetleveltype_id);
    }


    /////////////////////////////////////////////////////////////////////////////
    // init - convenience function to set all edge attribute values at once
    public function init($id, $sourcenode_id, $targetnode_id, $width, $sourceleveltype_id, $targetleveltype_id) {
        $this->setID($id);
        $this->setSourcenodeID($sourcenode_id);
        $this->setTargetnodeID($targetnode_id);
        $this->setWidth($width);
        $this->setSourceleveltypeID($sourceleveltype_id);
        $this->setTargetleveltypeID($targetleveltype_id);
    }





    /////////////////////////////////////////////////////////////////////////////
    // load - initialize the edge by loading it from the database based on the
    // specificed id
    public function load($id) {
        $sql = "SELECT id, sourcenode_id, targetnode_id, width, sourceleveltype_id, targetleveltype_id " .
             "FROM " . Edge::$TABLE_EDGE . " where id = " . $id;
        $result = DatabaseManager::getInstance()->query($sql);

        if ($row = $result->fetch_assoc()) {
            $this->init($row['id'], $row['sourcenode_id'], $row['targetnode_id'], $row['width'],
                        $row['sourceleveltype_id'], $row['targetleveltype_id']);
        } else {
            return false;
        }
        return true;
    }

    /////////////////////////////////////////////////////////////////////////////
    // insert - insert a new edge into the database and return the new Edge
    // Don't insert if it already exists.
    public function insert() {

        if ($this->existsInDatabase()) {
            //print("edge already exists in database\n");
            return false;
        }

        $sql = "CALL insertEdge({$this->getSourcenodeID()}, " .
                               "{$this->getTargetnodeID()}, " .
                               "{$this->getWidth()}, " .
                               "{$this->getSourceleveltypeID()}, " .
                               "{$this->getTargetleveltypeID()}, " .
                               "@newEdgeID)";
        $result = DatabaseManager::getInstance()->query($sql);
        $sql = "select @newEdgeID";
        $result = DatabaseManager::getInstance()->query($sql);
        if ($row = $result->fetch_assoc()) {
            $this->setID($row["@newEdgeID"]);
        } else {
            // some error handling?
            return false;
        }
        return true;
    }

    /////////////////////////////////////////////////////////////////////////////
    // update - update the edge to the database (edge already exists in database)
    public function update() {
        $sql = "UPDATE " . Edge::$TABLE_EDGE . " set sourcenode_id = {$this->getSourcenodeID()}, " .
                                   "targetnode_id = {$this->getTargetnodeID()}, " .
                                   "width = {$this->getWidth()}, " .
                                   "sourceleveltype_id = {$this->getSourceleveltypeID()}, " .
                                   "targetleveltype_id = {$this->getTargetleveltypeID()} " .
               "WHERE id = {$this->getID()}";
        $result = DatabaseManager::getInstance()->query($sql);
    }

    /////////////////////////////////////////////////////////////////////////////
    // delete - delete the edge from the database
    public function delete() {
        $sql = "DELETE FROM " . Edge::$TABLE_EDGE . " WHERE id = {$this->getID()}";
        $result = DatabaseManager::getInstance()->query($sql);
        return true;
    }

    /////////////////////////////////////////////////////////////////////////////
    // existsInDatabase - checks to see if edge already exists in database -
    // meaning a edge record with sourcenode_id and targetnode_id both equal to
    // the values in this Edge object.
    public function existsInDatabase() {
        $edgeCount = 0;
        $sql = "SELECT count(*) as edge_count FROM " . Edge::$TABLE_EDGE .
               " where sourcenode_id = {$this->getSourcenodeID()} and targetnode_id = {$this->getTargetnodeID()} ";
        $result = DatabaseManager::getInstance()->query($sql);
        if ($row = $result->fetch_assoc()) {
            $edgeCount = $row['edge_count'];
        } else {
            // some error handling?
        }
        if ($edgeCount < 1) {
            return false;
        }
        return true;
    }

    /////////////////////////////////////////////////////////////////////////////
    // getID - get the edge id
    public function getID() {
        return $this->id;
    }

    /////////////////////////////////////////////////////////////////////////////
    // setID - set the edge id
    public function setID($id) {
        $this->id = $id;
    }

    /////////////////////////////////////////////////////////////////////////////
    // getSourcenodeID - get the source node id
    public function getSourcenodeID() {
        return $this->sourcenode_id;
    }

    /////////////////////////////////////////////////////////////////////////////
    // setSourcenodeID - set the source node id
    public function setSourcenodeID($sourcenode_id) {
        $this->sourcenode_id = $sourcenode_id;
    }

    /////////////////////////////////////////////////////////////////////////////
    // getTargetnodeID - get the target node id
    public function getTargetnodeID() {
        return $this->targetnode_id;
    }

    /////////////////////////////////////////////////////////////////////////////
    // setTargetnodeID - set the target node id
    public function setTargetnodeID($targetnode_id) {
        $this->targetnode_id = $targetnode_id;
    }

    /////////////////////////////////////////////////////////////////////////////
    // getWidth - get the width
    public function getWidth() {
        return $this->width;
    }

    /////////////////////////////////////////////////////////////////////////////
    // setWidth - set the width
    public function setWidth($width) {
        $this->width = $width;
    }

    /////////////////////////////////////////////////////////////////////////////
    // getSourceleveltypeID - get the source level type id
    public function getSourceleveltypeID() {
        return $this->sourceleveltype_id;
    }
    /////////////////////////////////////////////////////////////////////////////
    // setSourceleveltypeID - set the source level type id
    public function setSourceleveltypeID($sourceleveltype_id) {
        $this->sourceleveltype_id = $sourceleveltype_id;
    }

    /////////////////////////////////////////////////////////////////////////////
    // getTargetleveltypeID - get the target level type id
    public function getTargetleveltypeID() {
        return $this->targetleveltype_id;
    }
    /////////////////////////////////////////////////////////////////////////////
    // setTargetleveltypeID - set the target level type id
    public function setTargetleveltypeID($targetleveltype_id) {
        $this->targetleveltype_id = $targetleveltype_id;
    }


    /////////////////////////////////////////////////////////////////////////////
    // printEdgeData - debug function that dumps edge attributes
    public function printEdgeData($desc = "") {
        //print("--------------------------------------------\n");
        print("{$desc}:\n");
        print("\tID: {$this->getID()}\n");
        print("\tSOURCE NODE ID: {$this->getSourcenodeID()}\n");
        print("\tTARGET NODE ID: {$this->getTargetnodeID()}\n");
        print("\tWIDTH: {$this->getWidth()}\n");
        print("\tSOURCE NODE LEVEL TYPE ID: {$this->getSourceleveltypeID()}\n");
        print("\tTARGET NODE LEVEL TYPE ID: {$this->getTargetleveltypeID()}\n");
    }






    // Static functions - mainly for retrieving groups of edges

    /////////////////////////////////////////////////////////////////////////////
    public static function loadEdges($condition) {
        $edges = array();
        $sql = "SELECT id, sourcenode_id, targetnode_id, width, sourceleveltype_id, targetleveltype_id FROM " .
               Edge::$TABLE_EDGE . " where " .
               $condition;

        // print("sql: {$sql}\n");

        $result = DatabaseManager::getInstance()->query($sql);
        while ($row = $result->fetch_assoc()) {
            $edge = new Edge($row['id'],
                             $row['sourcenode_id'],
                             $row['targetnode_id'],
                             $row['width'],
                             $row['sourceleveltype_id'],
                             $row['targetleveltype_id']);
            $edges[] = $edge;
        }
        return $edges;
    }

    /////////////////////////////////////////////////////////////////////////////
    public static function loadEdgeBetweenTwoNodes($node1, $node2) {
        return Edge::loadEdgeBetweenTwoNodesByNodeID($node1->getID(), $node2->getID());
    }

    /////////////////////////////////////////////////////////////////////////////
    // This should only EVER find one or zero records.
    public static function loadEdgeBetweenTwoNodesByNodeID($nodeID1, $nodeID2) {
        $edges = Edge::loadEdges("(sourcenode_id = {$nodeID1} and targetnode_id = {$nodeID2}) ".
                               " OR " .
                               "(sourcenode_id = {$nodeID2} and targetnode_id = {$nodeID1}) ");
        if (count($edges) == 0) {
            return null;
        }
        if (count($edges) > 1) {
            die ("Data inconsistency found: more than one edge record between nodes " .
                 "{$nodeID1} and {$nodeID2} found.  This should be manually corrected!\n");
        }
        return $edges[0];
    }















    /////////////////////////////////////////////////////////////////////////////
    // getEdgesForNodes - given two lists of nodes, return an array of
    // all possible edge records.
    //
    // Each edge record is based on a source node from the first list of
    // nodes and a target node from the second list of nodes.
    // All possible edges between all source and target pairs are returned.
    //
    // Each edge record contains three elements:
    // - source node ID
    // - target node ID
    // - width value (count of common leaf records)
    //
    // The ID, source node level type ID, and target node level type ID
    // fields are not included.
    //
    // Returns an array.  Array does not include edges of width=0.
    //
    // This is a maintenance method meant for updateEdges.php.

    public static function getEdgesForNodes($nodes1, $nodes2) {
        // get all leafs of all nodes supplied:

        $allleafs1 = Leaf::loadLeafsForNodes($nodes1);
        $allleafs2 = Leaf::loadLeafsForNodes($nodes2);


        $edges = array();
        foreach ($nodes1 as $node1) {
            foreach ($nodes2 as $node2) {
                if ($node1->getID() == $node2->getID()) {  // theoretically shouldn't happen
                    break;
                }
                $leafs1 = array();
                if (array_key_exists($node1->getID(), $allleafs1)) {
                    $leafs1 = $allleafs1[$node1->getID()];
                }
                $leafs2 = array();
                if (array_key_exists($node2->getID(), $allleafs2)) {
                    $leafs2 = $allleafs2[$node2->getID()];
                }

                if ((count($leafs1) > 0) && (count($leafs2) > 0)) {
                    //$tmpedgeFullData = Leaf::getSharedLeafsOfTwoNodes($leafs1, $leafs2);
                    $tmpSharedLeafObjectIDs = Leaf::getSharedLeafObjectIDsOfTwoLeafLists($leafs1, $leafs2);
                    if (count($tmpSharedLeafObjectIDs) > 0) {
                        $tmpEdge = new Edge();
                        $tmpEdge->setSourcenodeID($node1->getID());
                        $tmpEdge->setTargetnodeID($node2->getID());
                        $tmpEdge->setWidth(count($tmpSharedLeafObjectIDs));
                        $edges[] = $tmpEdge;
                    }
                }

            }
        }
        return $edges;
    }


    /////////////////////////////////////////////////////////////////////////////
    // bulkSaveEdgeRecords - save edge records to edge database table
    //
    // edge table contains:
    //    sourcenode_id
    //    targetnode_id
    //    width
    //    sourceleveltype_id
    //    targetleveltype_id
    //
    // Notes:
    //        - Meant for maintenance processes.
    //        - Execution should be preceeded by a call to deleteEdgeRecords(leveltype1, leveltype2).
    //
    public static function bulkSaveEdgeRecords($leveltype1, $leveltype2, $edges, $tablename=EDGE::TABLE_EDGE) {
        $sql = "insert into {$tablename} " .
                     "(sourcenode_id,targetnode_id,width,sourceleveltype_id,targetleveltype_id) " .
                     "values (?, ?, ?, ?, ?) ";
        $insertStatement = DatabaseManager::getInstance()->getDatabaseConnection()->prepare($sql);

        $rowsInserted = 0;
        $rowsNotInserted = 0;

        foreach ($edges as $edge) {
            $insertStatement->bind_param('iiiii', $edge->getSourcenodeID(),
                                                  $edge->getTargetnodeID(),
                                                  $edge->getWidth(),
                                                  $leveltype1,
                                                  $leveltype2);
            if ($insertStatement->execute()) {
                $rowsInserted = $rowsInserted + $insertStatement->affected_rows;
            } else {
                $rowsNotInserted++;
            }
        }
        $insertStatement->close();
        return array($rowsInserted, $rowsNotInserted);
    }



/*
    /////////////////////////////////////////////////////////////////////////////
    // deleteEdgeRecords - delete edge records from the edge table nased on
    // source and target node levels
    //
    public static function deleteEdgeRecords($leveltype1, $leveltype2) {
        $sql = "delete from edge where sourceleveltype_id = " . $leveltype1 .
                     " and targetleveltype_id = " . $leveltype2;
        $result = DatabaseManager::getInstance()->query($sql);
        return DatabaseManager::getInstance()->getDatabaseConnection()->affected_rows;

    }
*/










    /////////////////////////////////////////////////////////////////////////////
    public static function printEdgesData($edges, $desc = "") {
        print($desc . "\n");
        $i = 0;
        foreach ($edges as $edge) {
            $i++;
            $edge->printEdgeData("Edge " . $i);
        }
        print(count($edges) . " edges\n");
    }


}
